#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

#define HERE_DELIM_MAX      256



#undef PEDANTIC_OCTAL

#define BASH_BASE_ERROR     65
#define BASH_BASE_DECIMAL   66
#define BASH_BASE_HEX     67
#ifdef PEDANTIC_OCTAL
#define BASH_BASE_OCTAL     68
#define BASH_BASE_OCTAL_ERROR 69
#endif


#define BASH_CMD_BODY     0
#define BASH_CMD_START      1
#define BASH_CMD_WORD     2
#define BASH_CMD_TEST     3
#define BASH_CMD_ARITH      4
#define BASH_CMD_DELIM      5



#define BASH_DELIM_LITERAL    0
#define BASH_DELIM_STRING   1
#define BASH_DELIM_CSTRING    2
#define BASH_DELIM_LSTRING    3
#define BASH_DELIM_COMMAND    4
#define BASH_DELIM_BACKTICK   5

#define BASH_DELIM_STACK_MAX  7

static inline int translateBashDigit( int ch ) {
  if( ch >= '0' && ch <= '9' ) {
    return ch - '0';
  } else if( ch >= 'a' && ch <= 'z' ) {
    return ch - 'a' + 10;
  } else if( ch >= 'A' && ch <= 'Z' ) {
    return ch - 'A' + 36;
  } else if( ch == '@' ) {
    return 62;
  } else if( ch == '_' ) {
    return 63;
  }
  return BASH_BASE_ERROR;
}

static inline int getBashNumberBase( char *s ) {
  int i = 0;
  int base = 0;
  while( *s ) {
    base = base * 10 + ( *s++ - '0' );
    i++;
  }
  if( base > 64 || i > 2 ) {
    return BASH_BASE_ERROR;
  }
  return base;
}

static int opposite( int ch ) {
  if( ch == '(' ) {
    return ')';
  }
  if( ch == '[' ) {
    return ']';
  }
  if( ch == '{' ) {
    return '}';
  }
  if( ch == '<' ) {
    return '>';
  }
  return ch;
}

static void ColouriseBashDoc( unsigned int startPos, int length, int initStyle,
                              WordList *keywordlists[], Accessor &styler ) {
  WordList &keywords = *keywordlists[0];
  WordList cmdDelimiter, bashStruct, bashStruct_in;
  cmdDelimiter.Set( "| || |& & && ; ;; ( ) { }" );
  bashStruct.Set( "if elif fi while until else then do done esac eval" );
  bashStruct_in.Set( "for case select" );
  CharacterSet setWordStart( CharacterSet::setAlpha, "_" );
  CharacterSet setWord( CharacterSet::setAlphaNum, "._+-" );
  CharacterSet setMetaCharacter( CharacterSet::setNone, "|&;()<> \t\r\n" );
  setMetaCharacter.Add( 0 );
  CharacterSet setBashOperator( CharacterSet::setNone, "^&%()-+=|{}[]:;>,*/<?!.~@" );
  CharacterSet setSingleCharOp( CharacterSet::setNone, "rwxoRWXOezsfdlpSbctugkTBMACahGLNn" );
  CharacterSet setParam( CharacterSet::setAlphaNum, "$_" );
  CharacterSet setHereDoc( CharacterSet::setAlpha, "_\\-+!" );
  CharacterSet setHereDoc2( CharacterSet::setAlphaNum, "_-+!" );
  CharacterSet setLeftShift( CharacterSet::setDigits, "=$" );
  class HereDocCls {
    public:
      int State;


      int Quote;
      bool Quoted;
      bool Indent;
      int DelimiterLength;
      char *Delimiter;
      HereDocCls() {
        State = 0;
        Quote = 0;
        Quoted = false;
        Indent = 0;
        DelimiterLength = 0;
        Delimiter = new char[HERE_DELIM_MAX];
        Delimiter[0] = '\0';
      }
      void Append( int ch ) {
        Delimiter[DelimiterLength++] = static_cast<char>( ch );
        Delimiter[DelimiterLength] = '\0';
      }
      ~HereDocCls() {
        delete []Delimiter;
      }
  };
  HereDocCls HereDoc;
  class QuoteCls {
    public:
      int Count;
      int Up, Down;
      QuoteCls() {
        Count = 0;
        Up    = '\0';
        Down  = '\0';
      }
      void Open( int u ) {
        Count++;
        Up    = u;
        Down  = opposite( Up );
      }
      void Start( int u ) {
        Count = 0;
        Open( u );
      }
  };
  QuoteCls Quote;
  class QuoteStackCls {
    public:
      int Count;
      int Up, Down;
      int Style;
      int Depth;
      int *CountStack;
      int *UpStack;
      int *StyleStack;
      QuoteStackCls() {
        Count = 0;
        Up    = '\0';
        Down  = '\0';
        Style = 0;
        Depth = 0;
        CountStack = new int[BASH_DELIM_STACK_MAX];
        UpStack    = new int[BASH_DELIM_STACK_MAX];
        StyleStack = new int[BASH_DELIM_STACK_MAX];
      }
      void Start( int u, int s ) {
        Count = 1;
        Up    = u;
        Down  = opposite( Up );
        Style = s;
      }
      void Push( int u, int s ) {
        if( Depth >= BASH_DELIM_STACK_MAX ) {
          return;
        }
        CountStack[Depth] = Count;
        UpStack   [Depth] = Up;
        StyleStack[Depth] = Style;
        Depth++;
        Count = 1;
        Up    = u;
        Down  = opposite( Up );
        Style = s;
      }
      void Pop( void ) {
        if( Depth <= 0 ) {
          return;
        }
        Depth--;
        Count = CountStack[Depth];
        Up    = UpStack   [Depth];
        Style = StyleStack[Depth];
        Down  = opposite( Up );
      }
      ~QuoteStackCls() {
        delete []CountStack;
        delete []UpStack;
        delete []StyleStack;
      }
  };
  QuoteStackCls QuoteStack;
  int numBase = 0;
  int digit;
  unsigned int endPos = startPos + length;
  int cmdState = BASH_CMD_START;
  int testExprType = 0;
  int ln = styler.GetLine( startPos );
  if( ln > 0 && startPos == static_cast<unsigned int>( styler.LineStart( ln ) ) ) {
    ln--;
  }
  for( ;; ) {
    startPos = styler.LineStart( ln );
    if( ln == 0 || styler.GetLineState( ln ) == BASH_CMD_START ) {
      break;
    }
    ln--;
  }
  initStyle = SCE_SH_DEFAULT;
  StyleContext sc( startPos, endPos - startPos, initStyle, styler );
  for( ; sc.More(); sc.Forward() ) {
    if( sc.atLineStart ) {
      ln = styler.GetLine( sc.currentPos );
      if( sc.state == SCE_SH_STRING
          || sc.state == SCE_SH_BACKTICKS
          || sc.state == SCE_SH_CHARACTER
          || sc.state == SCE_SH_HERE_Q
          || sc.state == SCE_SH_COMMENTLINE
          || sc.state == SCE_SH_PARAM ) {
        styler.SetLineState( ln, BASH_CMD_BODY );
      } else {
        if( ln > 0 ) {
          if( ( sc.GetRelative( -3 ) == '\\' && sc.GetRelative( -2 ) == '\r' && sc.chPrev == '\n' )
              || sc.GetRelative( -2 ) == '\\' ) {
          } else
          { cmdState = BASH_CMD_START; }
        }
        styler.SetLineState( ln, cmdState );
      }
    }
    int cmdStateNew = BASH_CMD_BODY;
    if( cmdState == BASH_CMD_TEST || cmdState == BASH_CMD_ARITH || cmdState == BASH_CMD_WORD ) {
      cmdStateNew = cmdState;
    }
    int stylePrev = sc.state;
    switch( sc.state ) {
      case SCE_SH_OPERATOR:
        sc.SetState( SCE_SH_DEFAULT );
        if( cmdState == BASH_CMD_DELIM ) {
          cmdStateNew = BASH_CMD_START;
        } else if( sc.chPrev == '\\' ) {
          cmdStateNew = cmdState;
        }
        break;
      case SCE_SH_WORD:
        if( !setWord.Contains( sc.ch ) ) {
          char s[500];
          char s2[10];
          sc.GetCurrent( s, sizeof( s ) );
          s2[0] = static_cast<char>( sc.ch );
          s2[1] = '\0';
          bool keywordEnds = IsASpace( sc.ch ) || cmdDelimiter.InList( s2 );
          if( cmdState == BASH_CMD_WORD ) {
            if( strcmp( s, "in" ) == 0 && keywordEnds )
            { cmdStateNew = BASH_CMD_BODY; }
            else if( strcmp( s, "do" ) == 0 && keywordEnds )
            { cmdStateNew = BASH_CMD_START; }
            else
            { sc.ChangeState( SCE_SH_IDENTIFIER ); }
            sc.SetState( SCE_SH_DEFAULT );
            break;
          }
          if( strcmp( s, "test" ) == 0 ) {
            if( cmdState == BASH_CMD_START && keywordEnds ) {
              cmdStateNew = BASH_CMD_TEST;
              testExprType = 0;
            } else
            { sc.ChangeState( SCE_SH_IDENTIFIER ); }
          } else if( bashStruct.InList( s ) ) {
            if( cmdState == BASH_CMD_START && keywordEnds )
            { cmdStateNew = BASH_CMD_START; }
            else
            { sc.ChangeState( SCE_SH_IDENTIFIER ); }
          } else if( bashStruct_in.InList( s ) ) {
            if( cmdState == BASH_CMD_START && keywordEnds )
            { cmdStateNew = BASH_CMD_WORD; }
            else
            { sc.ChangeState( SCE_SH_IDENTIFIER ); }
          } else if( s[0] == '-' ) {
            if( cmdState != BASH_CMD_TEST )
            { sc.ChangeState( SCE_SH_IDENTIFIER ); }
          } else if( cmdState != BASH_CMD_START
                     || !( keywords.InList( s ) && keywordEnds ) ) {
            sc.ChangeState( SCE_SH_IDENTIFIER );
          }
          sc.SetState( SCE_SH_DEFAULT );
        }
        break;
      case SCE_SH_IDENTIFIER:
        if( sc.chPrev == '\\' ) {
          sc.ForwardSetState( SCE_SH_DEFAULT );
        } else if( !setWord.Contains( sc.ch ) ) {
          sc.SetState( SCE_SH_DEFAULT );
        }
        break;
      case SCE_SH_NUMBER:
        digit = translateBashDigit( sc.ch );
        if( numBase == BASH_BASE_DECIMAL ) {
          if( sc.ch == '#' ) {
            char s[10];
            sc.GetCurrent( s, sizeof( s ) );
            numBase = getBashNumberBase( s );
            if( numBase != BASH_BASE_ERROR )
            { break; }
          } else if( IsADigit( sc.ch ) ) {
            break;
          }
        } else if( numBase == BASH_BASE_HEX ) {
          if( IsADigit( sc.ch, 16 ) ) {
            break;
          }
          #ifdef PEDANTIC_OCTAL
        } else if( numBase == BASH_BASE_OCTAL ||
                   numBase == BASH_BASE_OCTAL_ERROR ) {
          if( digit <= 7 ) {
            break;
          }
          if( digit <= 9 ) {
            numBase = BASH_BASE_OCTAL_ERROR;
            break;
          }
          #endif
        } else if( numBase == BASH_BASE_ERROR ) {
          if( digit <= 9 ) {
            break;
          }
        } else {
          if( digit != BASH_BASE_ERROR ) {
            if( numBase <= 36 ) {
              if( digit >= 36 )
              { digit -= 26; }
            }
            if( digit < numBase )
            { break; }
            if( digit <= 9 ) {
              numBase = BASH_BASE_ERROR;
              break;
            }
          }
        }
        if( numBase == BASH_BASE_ERROR
          #ifdef PEDANTIC_OCTAL
            || numBase == BASH_BASE_OCTAL_ERROR
          #endif
          ) {
          sc.ChangeState( SCE_SH_ERROR );
        }
        sc.SetState( SCE_SH_DEFAULT );
        break;
      case SCE_SH_COMMENTLINE:
        if( sc.atLineEnd && sc.chPrev != '\\' ) {
          sc.SetState( SCE_SH_DEFAULT );
        }
        break;
      case SCE_SH_HERE_DELIM:
        if( HereDoc.State == 0 ) {
          HereDoc.Quote = sc.chNext;
          HereDoc.Quoted = false;
          HereDoc.DelimiterLength = 0;
          HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
          if( sc.chNext == '\'' || sc.chNext == '\"' ) {
            sc.Forward();
            HereDoc.Quoted = true;
            HereDoc.State = 1;
          } else if( setHereDoc.Contains( sc.chNext ) ) {
            HereDoc.State = 1;
          } else if( sc.chNext == '<' ) {
            sc.Forward();
            sc.ForwardSetState( SCE_SH_DEFAULT );
          } else if( IsASpace( sc.chNext ) ) {
          } else if( setLeftShift.Contains( sc.chNext ) ) {
            sc.ChangeState( SCE_SH_OPERATOR );
            sc.ForwardSetState( SCE_SH_DEFAULT );
          } else
          { HereDoc.State = 1; }
        } else if( HereDoc.State == 1 ) {
          if( ( HereDoc.Quote == '\'' && sc.ch != HereDoc.Quote ) ||
              ( HereDoc.Quoted && sc.ch != HereDoc.Quote && sc.ch != '\\' ) ||
              ( HereDoc.Quote != '\'' && sc.chPrev == '\\' ) ||
              ( setHereDoc2.Contains( sc.ch ) ) ) {
            HereDoc.Append( sc.ch );
          } else if( HereDoc.Quoted && sc.ch == HereDoc.Quote ) {
            sc.ForwardSetState( SCE_SH_DEFAULT );
          } else if( sc.ch == '\\' ) {
            if( HereDoc.Quoted && sc.chNext != HereDoc.Quote && sc.chNext != '\\' )
            { HereDoc.Append( sc.ch ); }
            else {
            }
          } else if( !HereDoc.Quoted ) {
            sc.SetState( SCE_SH_DEFAULT );
          }
          if( HereDoc.DelimiterLength >= HERE_DELIM_MAX - 1 ) {
            sc.SetState( SCE_SH_ERROR );
            HereDoc.State = 0;
          }
        }
        break;
      case SCE_SH_HERE_Q:
        if( sc.atLineStart ) {
          sc.SetState( SCE_SH_HERE_Q );
          int prefixws = 0;
          while( sc.ch == '\t' && !sc.atLineEnd ) {
            sc.Forward();
            prefixws++;
          }
          if( prefixws > 0 ) {
            sc.SetState( SCE_SH_HERE_Q );
          }
          while( !sc.atLineEnd ) {
            sc.Forward();
          }
          char s[HERE_DELIM_MAX];
          sc.GetCurrent( s, sizeof( s ) );
          if( sc.LengthCurrent() == 0 ) {
            if( ( prefixws == 0 || HereDoc.Indent ) &&
                HereDoc.Quoted && HereDoc.DelimiterLength == 0 )
            { sc.SetState( SCE_SH_DEFAULT ); }
            break;
          }
          if( s[strlen( s ) - 1] == '\r' ) {
            s[strlen( s ) - 1] = '\0';
          }
          if( strcmp( HereDoc.Delimiter, s ) == 0 ) {
            if( ( prefixws == 0 ) ||
                ( prefixws > 0 && HereDoc.Indent ) ) {
              sc.SetState( SCE_SH_DEFAULT );
              break;
            }
          }
        }
        break;
      case SCE_SH_SCALAR:
        if( !setParam.Contains( sc.ch ) ) {
          if( sc.LengthCurrent() == 1 ) {
            sc.ForwardSetState( SCE_SH_DEFAULT );
          } else
          { sc.SetState( SCE_SH_DEFAULT ); }
        }
        break;
      case SCE_SH_STRING:
      case SCE_SH_BACKTICKS:
        if( sc.ch == '\\' && QuoteStack.Up != '\\' ) {
          if( QuoteStack.Style != BASH_DELIM_LITERAL ) {
            sc.Forward();
          }
        } else if( sc.ch == QuoteStack.Down ) {
          QuoteStack.Count--;
          if( QuoteStack.Count == 0 ) {
            if( QuoteStack.Depth > 0 )
            { QuoteStack.Pop(); }
            else
            { sc.ForwardSetState( SCE_SH_DEFAULT ); }
          }
        } else if( sc.ch == QuoteStack.Up ) {
          QuoteStack.Count++;
        } else {
          if( QuoteStack.Style == BASH_DELIM_STRING ||
              QuoteStack.Style == BASH_DELIM_LSTRING
            ) {
            if( sc.ch == '`' )
            { QuoteStack.Push( sc.ch, BASH_DELIM_BACKTICK ); }
            else if( sc.ch == '$' && sc.chNext == '(' ) {
              sc.Forward();
              QuoteStack.Push( sc.ch, BASH_DELIM_COMMAND );
            }
          } else if( QuoteStack.Style == BASH_DELIM_COMMAND ||
                     QuoteStack.Style == BASH_DELIM_BACKTICK
                   ) {
            if( sc.ch == '\'' )
            { QuoteStack.Push( sc.ch, BASH_DELIM_LITERAL ); }
            else if( sc.ch == '\"' )
            { QuoteStack.Push( sc.ch, BASH_DELIM_STRING ); }
            else if( sc.ch == '`' )
            { QuoteStack.Push( sc.ch, BASH_DELIM_BACKTICK ); }
            else if( sc.ch == '$' ) {
              if( sc.chNext == '\'' ) {
                sc.Forward();
                QuoteStack.Push( sc.ch, BASH_DELIM_CSTRING );
              } else if( sc.chNext == '\"' ) {
                sc.Forward();
                QuoteStack.Push( sc.ch, BASH_DELIM_LSTRING );
              } else if( sc.chNext == '(' ) {
                sc.Forward();
                QuoteStack.Push( sc.ch, BASH_DELIM_COMMAND );
              }
            }
          }
        }
        break;
      case SCE_SH_PARAM:
        if( sc.ch == '\\' && Quote.Up != '\\' ) {
          sc.Forward();
        } else if( sc.ch == Quote.Down ) {
          Quote.Count--;
          if( Quote.Count == 0 ) {
            sc.ForwardSetState( SCE_SH_DEFAULT );
          }
        } else if( sc.ch == Quote.Up ) {
          Quote.Count++;
        }
        break;
      case SCE_SH_CHARACTER:
        if( sc.ch == Quote.Down ) {
          Quote.Count--;
          if( Quote.Count == 0 ) {
            sc.ForwardSetState( SCE_SH_DEFAULT );
          }
        }
        break;
    }
    if( HereDoc.State == 1 && sc.atLineEnd ) {
      HereDoc.State = 2;
      if( HereDoc.Quoted ) {
        if( sc.state == SCE_SH_HERE_DELIM ) {
          sc.ChangeState( SCE_SH_ERROR );
        }
        sc.SetState( SCE_SH_HERE_Q );
      } else if( HereDoc.DelimiterLength == 0 ) {
        sc.ChangeState( SCE_SH_ERROR );
        sc.SetState( SCE_SH_DEFAULT );
      } else
      { sc.SetState( SCE_SH_HERE_Q ); }
    }
    if( stylePrev != SCE_SH_DEFAULT && sc.state == SCE_SH_DEFAULT ) {
      cmdState = cmdStateNew;
    }
    if( sc.state == SCE_SH_DEFAULT ) {
      if( sc.ch == '\\' ) {
        sc.SetState( SCE_SH_IDENTIFIER );
        if( sc.chNext == '\r' || sc.chNext == '\n' ) {
          sc.SetState( SCE_SH_OPERATOR );
        }
      } else if( IsADigit( sc.ch ) ) {
        sc.SetState( SCE_SH_NUMBER );
        numBase = BASH_BASE_DECIMAL;
        if( sc.ch == '0' ) {
          if( sc.chNext == 'x' || sc.chNext == 'X' ) {
            numBase = BASH_BASE_HEX;
            sc.Forward();
          } else if( IsADigit( sc.chNext ) ) {
            #ifdef PEDANTIC_OCTAL
            numBase = BASH_BASE_OCTAL;
            #else
            numBase = BASH_BASE_HEX;
            #endif
          }
        }
      } else if( setWordStart.Contains( sc.ch ) ) {
        sc.SetState( SCE_SH_WORD );
      } else if( sc.ch == '#' ) {
        if( stylePrev != SCE_SH_WORD && stylePrev != SCE_SH_IDENTIFIER &&
            ( sc.currentPos == 0 || setMetaCharacter.Contains( sc.chPrev ) ) ) {
          sc.SetState( SCE_SH_COMMENTLINE );
        } else
        { sc.SetState( SCE_SH_WORD ); }
      } else if( sc.ch == '\"' ) {
        sc.SetState( SCE_SH_STRING );
        QuoteStack.Start( sc.ch, BASH_DELIM_STRING );
      } else if( sc.ch == '\'' ) {
        sc.SetState( SCE_SH_CHARACTER );
        Quote.Start( sc.ch );
      } else if( sc.ch == '`' ) {
        sc.SetState( SCE_SH_BACKTICKS );
        QuoteStack.Start( sc.ch, BASH_DELIM_BACKTICK );
      } else if( sc.ch == '$' ) {
        if( sc.Match( "$((" ) ) {
          sc.SetState( SCE_SH_OPERATOR );
          continue;
        }
        sc.SetState( SCE_SH_SCALAR );
        sc.Forward();
        if( sc.ch == '{' ) {
          sc.ChangeState( SCE_SH_PARAM );
          Quote.Start( sc.ch );
        } else if( sc.ch == '\'' ) {
          sc.ChangeState( SCE_SH_STRING );
          QuoteStack.Start( sc.ch, BASH_DELIM_CSTRING );
        } else if( sc.ch == '"' ) {
          sc.ChangeState( SCE_SH_STRING );
          QuoteStack.Start( sc.ch, BASH_DELIM_LSTRING );
        } else if( sc.ch == '(' ) {
          sc.ChangeState( SCE_SH_BACKTICKS );
          QuoteStack.Start( sc.ch, BASH_DELIM_COMMAND );
        } else if( sc.ch == '`' ) {
          sc.ChangeState( SCE_SH_BACKTICKS );
          QuoteStack.Start( sc.ch, BASH_DELIM_BACKTICK );
        } else
        { continue; }
      } else if( sc.Match( '<', '<' ) ) {
        sc.SetState( SCE_SH_HERE_DELIM );
        HereDoc.State = 0;
        if( sc.GetRelative( 2 ) == '-' ) {
          HereDoc.Indent = true;
          sc.Forward();
        } else
        { HereDoc.Indent = false; }
      } else if( sc.ch == '-'  &&
                 setSingleCharOp.Contains( sc.chNext ) &&
                 !setWord.Contains( sc.GetRelative( 2 ) ) &&
                 IsASpace( sc.chPrev ) ) {
        sc.SetState( SCE_SH_WORD );
        sc.Forward();
      } else if( setBashOperator.Contains( sc.ch ) ) {
        char s[10];
        bool isCmdDelim = false;
        sc.SetState( SCE_SH_OPERATOR );
        if( cmdState == BASH_CMD_START
            || cmdState == BASH_CMD_BODY ) {
          if( sc.Match( '(', '(' ) ) {
            cmdState = BASH_CMD_ARITH;
            sc.Forward();
          } else if( sc.Match( '[', '[' ) && IsASpace( sc.GetRelative( 2 ) ) ) {
            cmdState = BASH_CMD_TEST;
            testExprType = 1;
            sc.Forward();
          } else if( sc.ch == '[' && IsASpace( sc.chNext ) ) {
            cmdState = BASH_CMD_TEST;
            testExprType = 2;
          }
        }
        if( cmdState == BASH_CMD_WORD && sc.Match( '(', '(' ) ) {
          cmdState = BASH_CMD_ARITH;
          sc.Forward();
          continue;
        }
        if( cmdState == BASH_CMD_START
            || cmdState == BASH_CMD_BODY
            || cmdState == BASH_CMD_WORD
            || ( cmdState == BASH_CMD_TEST && testExprType == 0 ) ) {
          s[0] = static_cast<char>( sc.ch );
          if( setBashOperator.Contains( sc.chNext ) ) {
            s[1] = static_cast<char>( sc.chNext );
            s[2] = '\0';
            isCmdDelim = cmdDelimiter.InList( s );
            if( isCmdDelim )
            { sc.Forward(); }
          }
          if( !isCmdDelim ) {
            s[1] = '\0';
            isCmdDelim = cmdDelimiter.InList( s );
          }
          if( isCmdDelim ) {
            cmdState = BASH_CMD_DELIM;
            continue;
          }
        }
        if( cmdState == BASH_CMD_ARITH && sc.Match( ')', ')' ) ) {
          cmdState = BASH_CMD_BODY;
          sc.Forward();
        } else if( cmdState == BASH_CMD_TEST && IsASpace( sc.chPrev ) ) {
          if( sc.Match( ']', ']' ) && testExprType == 1 ) {
            sc.Forward();
            cmdState = BASH_CMD_BODY;
          } else if( sc.ch == ']' && testExprType == 2 ) {
            cmdState = BASH_CMD_BODY;
          }
        }
      }
    }
  }
  sc.Complete();
  if( sc.state == SCE_SH_HERE_Q ) {
    styler.ChangeLexerState( sc.currentPos, styler.Length() );
  }
  sc.Complete();
}

static bool IsCommentLine( int line, Accessor &styler ) {
  int pos = styler.LineStart( line );
  int eol_pos = styler.LineStart( line + 1 ) - 1;
  for( int i = pos; i < eol_pos; i++ ) {
    char ch = styler[i];
    if( ch == '#' ) {
      return true;
    } else if( ch != ' ' && ch != '\t' ) {
      return false;
    }
  }
  return false;
}

static void FoldBashDoc( unsigned int startPos, int length, int, WordList *[],
                         Accessor &styler ) {
  bool foldComment = styler.GetPropertyInt( "fold.comment" ) != 0;
  bool foldCompact = styler.GetPropertyInt( "fold.compact", 1 ) != 0;
  unsigned int endPos = startPos + length;
  int visibleChars = 0;
  int skipHereCh = 0;
  int lineCurrent = styler.GetLine( startPos );
  int levelPrev = styler.LevelAt( lineCurrent ) & SC_FOLDLEVELNUMBERMASK;
  int levelCurrent = levelPrev;
  char chNext = styler[startPos];
  int styleNext = styler.StyleAt( startPos );
  for( unsigned int i = startPos; i < endPos; i++ ) {
    char ch = chNext;
    chNext = styler.SafeGetCharAt( i + 1 );
    int style = styleNext;
    styleNext = styler.StyleAt( i + 1 );
    bool atEOL = ( ch == '\r' && chNext != '\n' ) || ( ch == '\n' );
    if( foldComment && atEOL && IsCommentLine( lineCurrent, styler ) ) {
      if( !IsCommentLine( lineCurrent - 1, styler )
          && IsCommentLine( lineCurrent + 1, styler ) ) {
        levelCurrent++;
      } else if( IsCommentLine( lineCurrent - 1, styler )
                 && !IsCommentLine( lineCurrent + 1, styler ) ) {
        levelCurrent--;
      }
    }
    if( style == SCE_SH_OPERATOR ) {
      if( ch == '{' ) {
        levelCurrent++;
      } else if( ch == '}' ) {
        levelCurrent--;
      }
    }
    if( style == SCE_SH_HERE_DELIM ) {
      if( ch == '<' && chNext == '<' ) {
        if( styler.SafeGetCharAt( i + 2 ) == '<' ) {
          skipHereCh = 1;
        } else {
          if( skipHereCh == 0 ) {
            levelCurrent++;
          } else
          { skipHereCh = 0; }
        }
      }
    } else if( style == SCE_SH_HERE_Q && styler.StyleAt( i + 1 ) == SCE_SH_DEFAULT ) {
      levelCurrent--;
    }
    if( atEOL ) {
      int lev = levelPrev;
      if( visibleChars == 0 && foldCompact ) {
        lev |= SC_FOLDLEVELWHITEFLAG;
      }
      if( ( levelCurrent > levelPrev ) && ( visibleChars > 0 ) ) {
        lev |= SC_FOLDLEVELHEADERFLAG;
      }
      if( lev != styler.LevelAt( lineCurrent ) ) {
        styler.SetLevel( lineCurrent, lev );
      }
      lineCurrent++;
      levelPrev = levelCurrent;
      visibleChars = 0;
    }
    if( !isspacechar( ch ) ) {
      visibleChars++;
    }
  }
  int flagsNext = styler.LevelAt( lineCurrent ) & ~SC_FOLDLEVELNUMBERMASK;
  styler.SetLevel( lineCurrent, levelPrev | flagsNext );
}

static const char * const bashWordListDesc[] = {
  "Keywords",
  0
};

LexerModule lmBash( SCLEX_BASH, ColouriseBashDoc, "bash", FoldBashDoc, bashWordListDesc );
